<template>
  <div class="bgView">
    <div :class="['json-view', length ? 'closeable' : '']" :style="'font-size:' + fontSize + 'px'">
      <span @click="toggleClose" :class="['angle', innerclosed ? 'closed' : '']" v-if="length"> </span>
      <div class="content-wrap">
        <p class="first-line">
          <span v-if="jsonKey" class="json-key">"{{ jsonKey }}": </span>
          <span v-if="length">
            {{ prefix }}
            {{ innerclosed ? '...' + subfix : '' }}
            <span class="json-note">
              {{ innerclosed ? ' // count: ' + length : '' }}
            </span>
          </span>
          <span v-if="!length">{{ isArray ? '[]' : '{}' }}</span>
        </p>
        <div v-if="!innerclosed && length" class="json-body">
          <template v-for="(item, i) in items">
            <json-view :closed="closed" v-if="item.isJSON" :key="i" :json="item.value" :jsonKey="item.key" :isLast="i === items.length - 1" />
            <p class="json-item" v-else :key="i + 1">
              <span class="json-key">
                {{ isArray ? '' : '"' + item.key + '"' }}
              </span>
              :
              <span class="json-value">
                {{ item.value + (i === items.length - 1 ? '' : ',') }}
              </span>
            </p>
          </template>
          <span v-show="!innerclosed" class="body-line"></span>
        </div>
        <p v-if="!innerclosed && length" class="last-line">
          <span>{{ subfix }}</span>
        </p>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
  export default defineComponent({
    name: 'JsonView',
  })
</script>
<script lang="ts" setup>
  import { ref, defineComponent, computed } from 'vue'

  const props = defineProps({
    json: {
      type: [Object, Array],
      default: () => {},
    },
    jsonKey: {
      type: String,
      default: '',
    },
    closed: {
      type: Boolean,
      default: true,
    },
    isLast: {
      type: Boolean,
      default: true,
    },
    fontSize: {
      type: Number,
      default: 13,
    },
  })
  const innerclosed = ref(props.closed)
  const isArray = computed(() => props.json instanceof Array)
  const length = computed(() => {
    if (!props.json) {
      return 0
    }
    return isArray.value ? props.json.length : Object.keys(props.json).length
  })
  const subfix = computed(() => (isArray.value ? ']' : '}') + (props.isLast ? '' : ','))
  const prefix = computed(() => (isArray.value ? '[' : '{'))
  const items = computed(() => {
    if (isArray.value) {
      return props.json.map((item: any) => {
        const isJSON = isObjectOrArray(item)
        return {
          value: isJSON ? item : JSON.stringify(item),
          isJSON,
          key: '',
        }
      })
    }
    const json = props.json
    return Object.keys(json).map((key) => {
      const item = json[key]
      const isJSON = isObjectOrArray(item)
      return {
        value: isJSON ? item : JSON.stringify(item),
        isJSON,
        key,
      }
    })
  })
  function isObjectOrArray(source: any) {
    const type = Object.prototype.toString.call(source)
    const res = type === '[object Array]' || type === '[object Object]'
    return res
  }
  function toggleClose() {
    innerclosed.value = !innerclosed.value
  }
</script>

<style scoped>
  .bgView {
    background-color: #fafafa;
  }

  .json-view {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    white-space: nowrap;
    padding-left: 20px;
    box-sizing: border-box;
  }

  .json-note {
    color: #909399;
  }

  .json-key {
    color: rgb(147, 98, 15);
  }

  .json-value {
    color: rgb(24, 186, 24);
  }

  .json-item {
    margin: 0;
    padding-left: 20px;
  }

  .first-line {
    padding: 0;
    margin: 0;
  }

  .json-body {
    position: relative;
    padding: 0;
    margin: 0;
  }

  .json-body .body-line {
    position: absolute;
    height: 100%;
    width: 0;
    border-left: dashed 1px #bbb;
    top: 0;
    left: 2px;
  }

  .last-line {
    padding: 0;
    margin: 0;
  }

  .angle {
    position: absolute;
    display: block;
    cursor: pointer;
    float: left;
    width: 20px;
    text-align: center;
    left: 0;
  }

  .angle::after {
    content: '';
    display: inline-block;
    width: 0;
    height: 0;
    vertical-align: middle;
    border-top: solid 4px #333;
    border-left: solid 6px transparent;
    border-right: solid 6px transparent;
  }

  .angle.closed::after {
    border-left: solid 4px #333;
    border-top: solid 6px transparent;
    border-bottom: solid 6px transparent;
  }
</style>
